using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace CSharpExtensions
{
	#region Option type
	public static class OptionTypeExtension
	{
		public static bool IsNone<T>(this Option<T> option)
		{			
			return option.Equals(Option<T>.None);
		}
		public static Option<T> ToOption<T>(this T t)
		{
			return Option<T>.Some(t);
		}
        public static Option<T> ToSomeOrNone<T>(this T t)
            where T :class
        {
            if (t == null)
                return Option<T>.None;
            return Option<T>.Some(t);;
        }

        public static Option<TResult> Select<T, TResult>(this Option<T> source, Func<T, TResult> selector)
        {
            if (source.IsNone())
                return Option<TResult>.None;

            return Option.Some<TResult>(selector(source.Value));
        }
        public static Option<TResult> SelectMany<TSource,TSubResult, TResult>(this Option<TSource> source, Func<TSource, Option<TSubResult>> subResultSelector,Func<TSource, TSubResult, TResult> resultSelector)
        {
            if (source.IsNone())
                return Option<TResult>.None;
            var subResultOption = subResultSelector(source.Value);
            if (subResultOption.IsNone())
                return Option<TResult>.None;
            return Option.Some(resultSelector(source.Value, subResultOption.Value));

        }
        public static Option<TResult> SelectMany<TSource,TResult>(this Option<TSource> source, Func<TSource,Option<TResult>> transform)
        {
            if(source.IsNone())
                return Option<TResult>.None;
            return transform(source.Value); 
        }
        public static Option<TSource> Where<TSource>(this Option<TSource> source,	Func<TSource, bool> predicate)
        {
            if(source.IsNone())
                return source;
            return 
                    predicate(source.Value)?
                    source:
                    Option<TSource>.None;
        }
        public static TSource Return<TSource>(this Option<TSource> source, TSource defValue)
        {
            return source.HasValue ? source.Value : defValue;
        }
        public static TSource Return<TSource>(this Option<TSource> source, Func<TSource> defValueLazy)
        {
            return source.HasValue ? source.Value : defValueLazy();
        }

	}
    public struct Option
    {
        public static Option<T> Some<T>(T val)
        {
            return Option<T>.Some(val);
        }
        public static bool Equals<T>(Option<T> first, Option<T> second)
        {
            return first.IsEqual(second);
        }
    }
	public struct Option<T>
	{
		#region Static
		static object noneHashObj;
		static Option()
		{
			None = new Option<T>();
			noneHashObj=new object();
		}
		public readonly static Option<T> None;
		public static Option<T> Some(T value)
		{
			return new Option<T>(value);
		}
		#endregion

        public readonly bool HasValue;
		private Option(T value)
		{
			HasValue = true;
            innerValue = value;			
		}
		private T innerValue;
		
		public T Value
		{
			get
			{
				if (HasValue)
				{
					return innerValue;
				}
				throw new InvalidOperationException();
			}
		}
		public override bool Equals(object obj)
		{
			if (obj is Option<T>)
			{
				var other=(Option<T>)obj;
                return IsEqual(other);
			}
			return false;
		}
        public bool IsEqual(Option<T> other)
        {
            if (!this.HasValue)
            {
                return !other.HasValue;
            }
            else
            {
                if (other.HasValue)
                {
                    return Value.Equals(other.Value);
                }
                return false;
            }
        }
		public override int GetHashCode()
		{
            if (this.HasValue)
            {
                return Value.GetHashCode();
            }
			return noneHashObj.GetHashCode();
		}
	}
	#endregion
}